﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using System.Xml;
using System.ComponentModel;

namespace Catsland.Core
{
	public class Collider : CatComponent, Drawable
	{
		public CatsBoundingBox m_rootBounding;
		VertexBuffer m_vertexBuffer;
		public VertexPositionColor[] m_vertex;
		int[] m_vertexNum;
		Vector2[] m_ZBound;
		
        // TODO: add triager selector
        public TriagerInvoker m_triagerInvoker;
        public TriagerInvoker TriggerInvoker
        {
            get { return m_triagerInvoker; }
            set { m_triagerInvoker = value; }
        }
		HashSet<Collider> m_invokers;
		
        private bool m_advanceTrigger = true; // if true, enter, stay, exit events will be invoked, or only enter.
        public bool AdvantageTrigger
        {
            get { return m_advanceTrigger; }
            set { m_advanceTrigger = value; }
        }

		public enum ColliderType
		{
			Collider,
			NegitiveTrigger,
			PositiveTrigger
		};
		private ColliderType m_colliderType = ColliderType.Collider;
        public ColliderType ColliderTypeAttribute
        {
            get { return m_colliderType; }
            set { m_colliderType = value; }
        }

        public Vector2 XBound
        {
            get { return m_rootBounding.m_XBound; }
            set 
            { 
                m_rootBounding.m_XBound = value;
                Initialize(Mgr<Scene>.Singleton);
            }
        }

        public Vector2 YBound
        {
            get { return m_rootBounding.m_YBound; }
            set 
            { 
                m_rootBounding.m_YBound = value;
                Initialize(Mgr<Scene>.Singleton);
            }
        }

        public Vector2 ZBound
        {
            get { return m_rootBounding.m_heightRange; }
            set 
            { 
                m_rootBounding.m_heightRange = value;
                Initialize(Mgr<Scene>.Singleton);
            }
        }

		public Collider(GameObject gameObject)
			: base(gameObject)
		{
		}

		public override CatComponent CloneComponent(GameObject gameObject)
		{
			Collider collider = new Collider(gameObject);
			// clone bounding box?

			// TODO: correct?
			collider.m_rootBounding = m_rootBounding;
			collider.m_advanceTrigger = m_advanceTrigger;
			collider.m_triagerInvoker = m_triagerInvoker;

			return collider;
		}

		public override void BindToScene(Scene scene)
		{
			base.BindToScene(scene);

			scene._renderList.AddItem(this);
			scene._colliderList.AddItem(this);
		}

		// before Initialize, m_rootBounding should be set.
		public override void Initialize(Scene scene)
		{
			base.Initialize(scene);

			UpdateDebugBound();

            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor)
            {
                m_vertexBuffer = new VertexBuffer(Mgr<GraphicsDevice>.Singleton,
                    typeof(VertexPositionColor), m_vertex.Length, BufferUsage.None);
                m_vertexBuffer.SetData<VertexPositionColor>(m_vertex);
            }
			m_invokers = new HashSet<Collider>();

			if (scene != null)
			{
				scene._colliderList.AddItem(this);
                if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor)
                {
                    scene._debugDrawableList.AddItem(this);
                }
			}
		}

		public void UpdateDebugBound()
		{
			List<VertexPositionColor> vertex = new List<VertexPositionColor>();
			List<int> vertex_num = new List<int>();
			List<Vector2> ZBound = new List<Vector2>();
			// wfs
			Queue<CatsBoundingBox> searchQueue = new Queue<CatsBoundingBox>();
			searchQueue.Enqueue(m_rootBounding);
			while (searchQueue.Count > 0)
			{
				CatsBoundingBox curBoundingBox = searchQueue.Dequeue();
				// add base vertex
				foreach (Vector2 vector in curBoundingBox.m_convex)
				{
					vertex.Add(new VertexPositionColor(new Vector3(vector.X, 
						vector.Y * Mgr<Scene>.Singleton._yCos, 0.0f),
						Color.Red));
				}
				vertex.Add(new VertexPositionColor(new Vector3(curBoundingBox.m_convex[0].X,
					curBoundingBox.m_convex[0].Y * Mgr<Scene>.Singleton._yCos, 0.0f),
						Color.Red));
				vertex_num.Add(curBoundingBox.m_convex.Length + 1);
				// add low vertex
				ZBound.Add(new Vector2(curBoundingBox.m_heightRange.X, curBoundingBox.m_heightRange.Y));

				// add sub boundingbox
				if (curBoundingBox.m_subBounding != null)
				{
					foreach (CatsBoundingBox boundingBox in curBoundingBox.m_subBounding)
					{
						searchQueue.Enqueue(boundingBox);
					}
				}
			}
			// generate vertex
			m_vertex = vertex.ToArray();
			m_vertexNum = vertex_num.ToArray();
			m_ZBound = ZBound.ToArray();
		}

		public override void Update(int timeLastFrame)
		{
			base.Update(timeLastFrame);

			if (m_colliderType == ColliderType.PositiveTrigger)
			{
				// Scan for collide
				Collider[] colliders = Mgr<ColliderList>.Singleton.JudgeCollides(m_rootBounding,
					m_gameObject.Position, m_gameObject.Height, this);
				if (colliders != null)
				{
	
					HashSet<Collider> cur_collider = new HashSet<Collider>();
					
					foreach (Collider collider in colliders)
					{
						if (m_advanceTrigger == true)
						{
							if (m_invokers.Contains(collider))
							{
                                // if it has collide in last frame
								m_triagerInvoker.InTrigger(this, collider);
							}
							else
							{
                                // not appear in last frame
								m_triagerInvoker.EnterTrigger(this, collider);
								m_invokers.Add(collider);
							}
							cur_collider.Add(collider);
						}
						else
						{
							m_triagerInvoker.InTrigger(this, collider);
						}
					}
					if (m_advanceTrigger == true)
					{
						foreach (Collider collider in m_invokers)
						{
							if (!cur_collider.Contains(collider))
							{
                                // in last frame but not in current frame
								m_triagerInvoker.ExitTrigger(this, collider);
							}
						}
						m_invokers = cur_collider;
					}
				}
			}
		}

		public bool JudgeCollide(CatsBoundingBox boundingBox, Vector2 XYOffset, float ZOffest = 0.0f)
		{
			// TODO: Judge collide with bounding box
			return m_rootBounding.IsCollide(boundingBox,
				XYOffset, ZOffest,
				m_gameObject.Position,
				m_gameObject.Height);
		}

		void Drawable.Draw(int timeLastFrame)
		{
			// draw XYPlant boundingBox
			BasicEffect effect = Mgr<DebugTools>.Singleton.DrawEffect;
			effect.View = Mgr<Camera>.Singleton.m_view;
			effect.Projection = Mgr<Camera>.Singleton.m_projection;
			effect.VertexColorEnabled = false;
			

			foreach (EffectPass pass in effect.CurrentTechnique.Passes)
			{
				int baseIndex = 0;
				int i;

				Mgr<GraphicsDevice>.Singleton.SetVertexBuffer(m_vertexBuffer);

				for(i=0; i < m_vertexNum.Length; ++i)
				{
					int num = m_vertexNum[i];
					// draw base
					effect.World = Matrix.CreateTranslation(
						new Vector3(m_gameObject.Position.X,
						m_gameObject.Position.Y * Mgr<Scene>.Singleton._yCos,
						0.0f));
					effect.DiffuseColor = new Vector3(0.3f, 0.0f, 0.0f);
					pass.Apply();
					Mgr<GraphicsDevice>.Singleton.DrawUserPrimitives<VertexPositionColor>(
					PrimitiveType.LineStrip, m_vertex, baseIndex, num-1);

					// draw low bound
					effect.World = Matrix.CreateTranslation(
						new Vector3(m_gameObject.Position.X,
						m_gameObject.Position.Y * Mgr<Scene>.Singleton._yCos 
							+ (m_ZBound[i].X + m_gameObject.Height) * Mgr<Scene>.Singleton._ySin,
						0.0f));
					effect.DiffuseColor = new Vector3(0.3f, 0.6f, 0.3f);
					pass.Apply();
					Mgr<GraphicsDevice>.Singleton.DrawUserPrimitives<VertexPositionColor>(
					PrimitiveType.LineStrip, m_vertex, baseIndex, num - 1);

					// draw up bound
					effect.World = Matrix.CreateTranslation(
						new Vector3(m_gameObject.Position.X,
						m_gameObject.Position.Y * Mgr<Scene>.Singleton._yCos 
							+ (m_ZBound[i].Y + m_gameObject.Height) * Mgr<Scene>.Singleton._ySin,
						0.0f));
					effect.DiffuseColor = new Vector3(0.5f, 0.9f, 0.5f);
					pass.Apply();
					Mgr<GraphicsDevice>.Singleton.DrawUserPrimitives<VertexPositionColor>(
					PrimitiveType.LineStrip, m_vertex, baseIndex, num - 1);

					baseIndex += num;
				}
			}
		}

		void InvodeTriager(Collider invoker)
		{

		}

		public override bool SaveToNode(XmlNode node, XmlDocument doc)
		{
			XmlElement collider = doc.CreateElement("Collider");
			node.AppendChild(collider);

			collider.SetAttribute("isTrigger", "" + (m_colliderType == ColliderType.Collider ? false : true));
			// TODO: fix this
			collider.SetAttribute("invoker", "");

			m_rootBounding.SaveToNode(collider, doc);

			return true;
		}

        public override void ConfigureFromNode(XmlElement node, Scene scene, GameObject gameObject)
        {
            base.ConfigureFromNode(node, scene, gameObject);

            bool isTrigger = bool.Parse(node.GetAttribute("isTrigger"));
            m_colliderType = isTrigger ? ColliderType.PositiveTrigger : ColliderType.Collider;

            // TODO: invoker

            XmlNode boundingBox = node.SelectSingleNode("BoundingBox");
            m_rootBounding = CatsBoundingBox.LoadFromNode(boundingBox, scene, gameObject);
        }

		public override void Destroy()
		{
			Mgr<Scene>.Singleton._colliderList.RemoveItem(this);
#if DEBUG
			Mgr<Scene>.Singleton._debugDrawableList.RemoveItem(this);
#endif	
			base.Destroy();
		}

		public float GetDepth()
		{
			return m_gameObject.Position.Y;
		}

		public int CompareTo(object obj)
		{
			float otherDepth = ((Drawable)obj).GetDepth();
			float thisDepth = GetDepth();
			if (otherDepth > thisDepth)
			{
				return 1;
			}
			else if (otherDepth < thisDepth)
			{
				return -1;
			}
			return 0;
		}
	}
}
